/*
 * Graphic.h
 *
 *  Created on: 2012-9-15
 *      Author: yangentao@gmail.com
 */

#ifndef GRAPHIC_H_
#define GRAPHIC_H_

#include "headers.h"

#include "UiUtil.h"
#include "Image.h"

class Canvas {
private:
	HDC hdc;
	bool hdcOwned;

	Color penColor;
	HPEN pen;
	HPEN penOld;

	Color brushColor;
	HBRUSH brush;
	HBRUSH brushOld;

	HFONT font;
	HFONT fontOld;

	HBITMAP bitmap;
	HBITMAP bitmapOld;

	// Private so GraphicImpl objects can not be copied
	Canvas(const Canvas &);
	Canvas &operator=(const Canvas &);
public:
	Canvas();
	Canvas(HDC dc);
	Canvas(HWND wid);
	virtual ~Canvas();
public:
	void init(HWND wid);
	void init(HDC dc);
	void release();
	bool isEnable();
	inline operator HDC() {
		return hdc;
	}
	inline HDC getDC() {
		return hdc;
	}
public:
	void selectPenColor(Color fore);
	void selectBrushColor(Color back);
	void selectBrush(HBRUSH br = 0);
	void setFont(Font &font_);
	void clipRect(Rect rc);
	void clipRect(int x1, int y1, int x2, int y2);
	void clipNull();
	void clipRgn(Rgn& rgn);
public:
	int logPixelsY();
	int deviceHeightFont(int points);
public:
	inline void moveTo(int x_, int y_) {
		::MoveToEx(hdc, x_, y_, 0);
	}

	inline void lineTo(int x_, int y_) {
		::LineTo(hdc, x_, y_);
	}

	inline void polygon(Point* points, int count) {
		::Polygon(hdc, reinterpret_cast<POINT *>(points), count);
	}

	inline void rectangle(Rect rc) {
		::Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
	}

	inline void fillRectangle(Rect rc, Color back) {
		//	// Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
		//	// There is no need to allocate a brush either.
		//	::SetBkColor(hdc, back);
		//	::ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE, &rc, TEXT(""), 0, NULL);
		HBRUSH br = createBrush(back.AsLong());
		::FillRect(hdc, &rc, br);
	}
	inline void fillRectangle(Rect rc, HBRUSH br) {
		::FillRect(hdc, &rc, br);
	}
	inline void roundRectangle(Rect rc, int w, int h) {
		::RoundRect(hdc, rc.left, rc.top, rc.right, rc.bottom, w, h);
	}

	inline void ellipse(Rect rc) {
		::Ellipse(hdc, rc.left, rc.top, rc.right, rc.bottom);
	}

	inline void polyBezier(POINT* points, int ptCount) {
		::PolyBezier(hdc, points, ptCount);
	}
	inline void arc(Rect r, Point from, Point to) {
		::Arc(hdc, r.left, r.top, r.right, r.bottom, from.x, from.y, to.x, to.y);
	}
	//quadrant: 0/1/2/3
	void arc90(Point center, int radius, int quadrant);
	void bezierHorz(Point from, Point to, int seg = 2);
	void AlphaRectangle(Rect rc, int cornerSize, Color fill, int alphaFill, Color outline, int alphaOutline, int flags);
	void transparentBlt(Point p, Bitmap bmp, COLORREF transparentColor);
	void bitblt(Point p, Bitmap bmp);
public:
	inline void setTextColor(Color color) {
		::SetTextColor(hdc, color.toColorRef());
	}
	inline void setTextBackColor(Color color) {
		::SetBkColor(hdc, color.toColorRef());
	}
	inline void setTextBackTransparent(bool transparent) {
		::SetBkMode(hdc, transparent ? TRANSPARENT : OPAQUE);
	}
	void drawText(Rect rc, int ybase, String s, UINT fuOptions);
	void drawTextNoClip(Rect rc, int ybase, String s);
	void drawTextClipped(Rect rc, int ybase, String s);
	void drawTextTransparent(Rect rc, int ybase, String s);

	void measureWidths(const char *s, int len, int *positions);
	inline void measureWidths(const String& s, int *positions) {
		measureWidths(s.buffer(), s.length(), positions);
	}
	int textWidth(const char *s, int len);
	inline int textWidth(const String& s) {
		return textWidth(s.buffer(), s.length());
	}
	int charWidth(char ch);

	void textMetric(LPTEXTMETRIC tm);
	int textHeight();

	static void clearCache();

	static HPEN createPen(COLORREF color);
	static HBRUSH createBrush(COLORREF color);
	static HBRUSH sysBrush(int index);
};

inline Canvas::Canvas() :
		hdc(0), hdcOwned(false), pen(0), penOld(0), brush(0), brushOld(0), font(0), fontOld(0), bitmap(0), bitmapOld(0) {
}
inline Canvas::Canvas(HDC dc) :
		hdc(0), hdcOwned(false), pen(0), penOld(0), brush(0), brushOld(0), font(0), fontOld(0), bitmap(0), bitmapOld(0) {
	init(dc);
}
inline Canvas::Canvas(HWND w) :
		hdc(0), hdcOwned(false), pen(0), penOld(0), brush(0), brushOld(0), font(0), fontOld(0), bitmap(0), bitmapOld(0) {
	init(w);
}
inline Canvas::~Canvas() {
	release();
}
inline void Canvas::init(HWND w) {
	release();
//	hdc = ::CreateCompatibleDC(NULL); //TODO why compatible DC, not GetDC(window)?
	hdc = GetDC(w);
	hdcOwned = true;
	::SetTextAlign(hdc, TA_BASELINE);
}
inline void Canvas::init(HDC dc_) {
	release();
	hdc = dc_;
	::SetTextAlign(hdc, TA_BASELINE);
}

inline bool Canvas::isEnable() {
	return hdc != 0;
}

inline void Canvas::selectPenColor(Color fore) {
	if (pen && penColor == fore) {
		return;
	}
	penColor = fore;
	pen = createPen(fore);
	if (penOld) {
		SelectPen(hdc, pen);
	} else {
		penOld = SelectPen(hdc, pen);
	}
}

inline void Canvas::selectBrushColor(Color back) {
	if (brush && brushColor == back) {
		return;
	}
	brushColor = back;
	// Only ever want pure, non-dithered brushes
//	Color colourNearest = ::GetNearestColor(hdc, back);
	brush = createBrush(back);
	if (brushOld) {
		SelectBrush(hdc, brush);
	} else {
		brushOld = SelectBrush(hdc, brush);
	}
}
inline void Canvas::selectBrush(HBRUSH br) {
	if (br) {
		brush = 0;
		if (brushOld) {
			SelectBrush(hdc, br);
		} else {
			brushOld = SelectBrush(hdc, br);
		}
	} else {
		if (brushOld) {
			SelectBrush(hdc, brushOld);
			brushOld = 0;
		}
	}
}

inline void Canvas::setFont(Font &font_) {
	if (font_.GetID() != font) {
		if (fontOld) {
			SelectFont(hdc, font_);
		} else {
			fontOld = SelectFont(hdc, font_);
		}
		font = font_;
	}
}
inline void Canvas::clipRgn(Rgn& rgn){
	::SelectClipRgn(hdc, rgn.getRgn());
}
inline void Canvas::clipRect(Rect rc) {
	::IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
}
inline void Canvas::clipRect(int x1, int y1, int x2, int y2) {
	::IntersectClipRect(hdc, x1, y1, x2, y2);
}
inline void Canvas::clipNull() {
	::SelectClipRgn(hdc, 0);
}
inline int Canvas::logPixelsY() {
	return ::GetDeviceCaps(hdc, LOGPIXELSY);
}

inline int Canvas::deviceHeightFont(int points) {
	return ::MulDiv(points, logPixelsY(), 72);
}

inline void Canvas::drawTextNoClip(Rect rc, int ybase, String s) {
	drawText(rc, ybase, s, ETO_OPAQUE);
}

inline void Canvas::drawTextClipped(Rect rc, int ybase, String s) {
	drawText(rc, ybase, s, ETO_OPAQUE | ETO_CLIPPED);
}
inline void Canvas::drawTextTransparent(Rect rc, int ybase, String s) {
	setTextBackTransparent(true);
	drawText(rc, ybase, s, 0);
}

inline int Canvas::textWidth(const char *s, int len) {
	SIZE sz = { 0, 0 };
	const WideText tbuf(s, len);
	::GetTextExtentPoint32W(hdc, tbuf.buf, tbuf.len, &sz);
	return sz.cx;
}
inline int Canvas::charWidth(char ch) {
	SIZE sz;
	::GetTextExtentPoint32A(hdc, &ch, 1, &sz);
	return sz.cx;
}

inline void Canvas::textMetric(LPTEXTMETRIC tm) {
	int n =GetTextMetrics(hdc, tm);
}
inline int Canvas::textHeight() {
	TEXTMETRIC tm = { 0 };
	GetTextMetrics(hdc, &tm);
	return tm.tmHeight;
}
inline HBRUSH Canvas::sysBrush(int index) {
	return GetSysColorBrush(index);
}
#endif /* GRAPHIC_H_ */
